/**
 * @file hostchannel_ethernet.c
 * @brief Implements functions defined in the host channel module, specifically
 *        targeted towards working with a host that connects over an ethernet
 *        cable (ex: router).
 * @author Vamshi Ponnapalli
 */

/*******************************************************************************
 * Includes
 ******************************************************************************/
//#include "system.h"
//#include "events.h"

#include "hostchannel.h"
#include "hal_types.h"

#include "common.h"
#include "ioCC2530.h"
#include "ioconfig.h"
#include "network.h"
#include "dhcp.h"
#include "w5100.h"

#include "w5100_wizi.h"
#include "dhcp_wizi.h"

#include "eth_config.h"

#include <string.h>

/*******************************************************************************
 * Variable declarations
 ******************************************************************************/
/**
 * Standard broadcast address.
 */
//static const ip_address_t BROADCAST_IP_ADDRESS = {0xFF, 0xFF, 0xFF, 0xFF};
  
/**
 * Standard null IP address.
 */
static const ip_address_t NULL_IP_ADDRESS = {0x00, 0x00, 0x00, 0x00};

/**
 * Active socket number. The ethernet device supports up to 4 sockets, of which
 * the current implementation uses one. This variable keeps track of this socket
 * number.
 */
static const uint8 HOSTCHANNEL_SOCK = 0;

//Event handler node
//static events_handler_node_t hostchannel_eventHandlerNode;

//Host channel callback.
static CHANNEL_EVENT_HANDLER hostchannel_callback = NULL;

//Local port, and host ipaddress and port.
static uint16 hostchannel_localPort = 3232;
static ip_address_t hostchannel_hostIPAddress = {0x00, 0x00, 0x00, 0x00};
static uint16 hostchannel_hostPort = 2323;


//"Public" variables
uint16 hostchannel_State = CHANNEL_STATE_INIT;


/*******************************************************************************
 * Static function declarations (private to this file)
 ******************************************************************************/
/*******************************************************************************
 * Event handlers
 ******************************************************************************/
/**
 * Handles events raised by the ethernet port.
 */
static void __hostchannel_onEthernetEvent(void);

/*******************************************************************************
 * "Private" Methods
 ******************************************************************************/
/**
 * Configures I/O pins for the host channel.
 */
static void __hostchannel_configurePins(void);

/**
 * Initializes the host channel endpoint. This involves obtaining an IP address
 * from a dhcp server.
 *
 * @return SUCCESS if an IP address was successfully establised, FAILED 
 *         otherwise.
 */
static uint8 __hostchannel_initializeEndpoint(void);

/**
 * Attempts to communicate with the DHCP server and obtain an IP address.
 *
 * @return SUCCESS if an IP address was successfully establised, FAILED 
 *         otherwise.
 */
static uint8 __hostchannel_obtainIPAddress(void);

/**
 * Receives and returns a DHCP response message from the server.
 * 
 * @param pMessage A reference to the DHCP message returned by the server.
 *
 * @return The size of the message received.
 */
static uint16 __hostchannel_getDHCPResponse(dhcp_message_t *pMessage);

/**
 * Reads the number of raw bytes available in the Rx buffer. This operation will
 * reset any interrupt flags.
 *
 * @return The number of bytes available (including headers).
 */
static uint16 __hostchannel_getRawByteCount(void);
/*******************************************************************************
 * Function implementations
 ******************************************************************************/
/**
 * Initializes the necessary I/O pins for communication with the ethernet port.
 */
uint8 hostchannel_initialize(void)
{
  //Configure I/O pins
  __hostchannel_configurePins();
  
  //Reset the ethernet port
  w5100_reset(); 

  /** 
   * Set addressing mode to:
   * 1) Indirect bus interface (2 address bits, 8 data bits)
   * 2) Auto address increments - automatically increment address when reading/
   *    writing.
   */
  w5100_setAddressingMode(0x03); //Mode = Auto Increment | Indirect Bus 
 
   //!DEBUG
  //TODO: Re evaluate the need for interrupts.
  //Register to listen for P0 events.
  //hostchannel_eventHandlerNode.handler = __hostchannel_onEthernetEvent;
  //events_registerHandler(PORT0_EVENT, &hostchannel_eventHandlerNode);
  
  //Enable interrupts.
  w5100_setInterruptMask(0xCF); //Interrupt mask - enable all interrupts
  
  //Initialize Rx socket buffers
  w5100_initializeRxMemory(W5100_SOCK_SIZE_8KB, W5100_SOCK_SIZE_1KB, 
                           W5100_SOCK_SIZE_1KB, W5100_SOCK_SIZE_1KB);
  
  //Initialize Tx socket buffers  
  w5100_initializeTxMemory(W5100_SOCK_SIZE_8KB, W5100_SOCK_SIZE_1KB, 
                           W5100_SOCK_SIZE_1KB, W5100_SOCK_SIZE_1KB);
 
  //Obtain an IP address
  if(__hostchannel_initializeEndpoint() != SUCCESS)
  {
    //IP address could not be obtained. Return error code.
    hostchannel_State = CHANNEL_STATE_ERROR;
    return(ERROR_ETH_INIT_DHCP);
  }
  
  //Initialization was successful
  hostchannel_State = CHANNEL_STATE_READY;
  return(SUCCESS);
}
/******************************************************************************/
uint8 hostchannel_sendData(uint8 *pData, uint16 size)
{
  if(w5100_Transmit(HOSTCHANNEL_SOCK, pData, size) == SUCCESS)
  {
    return(SUCCESS);
  }
  else
  {
    //Transmit failed.
    return(ERROR_ETH_TX_FAILED);
  }
}
/******************************************************************************/
uint8 hostchannel_close()
{
  //Close the host channel socket.
  w5100_closeSocket(HOSTCHANNEL_SOCK);

  //Set the callback handler to null.
  hostchannel_callback = NULL;
  
  //Update the state of the channel
  hostchannel_State = CHANNEL_STATE_CLOSED;  
  return(SUCCESS);
}
/******************************************************************************/
/**
 * This routine assumes that there is an active connection available.
 */
uint16 hostchannel_getAvailableDataSize()
{
  uint16 size = __hostchannel_getRawByteCount();
  return(size);
}
/******************************************************************************/
uint8 hostchannel_receiveData(uint8 *pData, uint16 size)
{
  if(w5100_Receive(HOSTCHANNEL_SOCK, pData, size) == SUCCESS)
  {
    return(SUCCESS);
  }
  else
  {
    //Receive failed.
    return(ERROR_ETH_RX_FAILED);
  }
}
/******************************************************************************/
uint8 hostchannel_open(CHANNEL_EVENT_HANDLER handler)
{
  //Set the local port.
  w5100_setLocalPortNumber(HOSTCHANNEL_SOCK, hostchannel_localPort);
  
  //Set the destination IP address
  w5100_setRemoteIPAddress(HOSTCHANNEL_SOCK, hostchannel_hostIPAddress);
    
  //Set the destination port number
  w5100_setRemotePortNumber(HOSTCHANNEL_SOCK, hostchannel_hostPort);
  
  uint8 state = w5100_getSocketState(HOSTCHANNEL_SOCK); 
  if(state == W5100_SOCK_STATE_CLOSED)
  {
    //Open the socket in TCP mode
    w5100_openSocket(HOSTCHANNEL_SOCK, W5100_SOCK_MODE_TCP);
    
    //Connect to the host (try 5 times)
    w5100_connect(HOSTCHANNEL_SOCK, 5);
    
    uint8 tries = 5;
    do
    {
      state = w5100_getSocketState(HOSTCHANNEL_SOCK); 
      
      if(state == W5100_SOCK_STATE_ESTD)
      {
        //Store a reference to the callback.
        hostchannel_callback = handler;
        
        //Update the state of the channel
        hostchannel_State = CHANNEL_STATE_OPEN;
        
        return(SUCCESS);
      }
      //Wait for sometime before trying again.
      utils_sleepUs(1000);
    }
    while(--tries > 0);    
  }
  
  //Open failed.
  hostchannel_State = CHANNEL_STATE_ERROR;
  return(FAILED);  
}
/*******************************************************************************
 * "Private" function implementations
 ******************************************************************************/
static void __hostchannel_onEthernetEvent(void)
{  
//  //!DEBUG
//  uint8 events = __hostchannel_readEthernetEvents();
//    
//  if(01)
//  {
//    events = __hostchannel_readCommunicationState();
//    if(events&0x04)
//    {    
//      //Alert the callback listener.
//      if(hostchannel_callback != NULL)
//      {
//        //TODO: Pass in a meaningful event code.
//        hostchannel_callback(0x00);
//      }
//    }
//  }
}
/******************************************************************************/
static void __hostchannel_configurePins(void)
{  
  //Set the reset pin as an output pin
  IO_CONFIG_OUTPUT(W5100_CONTROL_PORT_NUM, W5100_RESET_PIN_NUM, 1);
  
  //Set chip select pin as an output pin
  IO_CONFIG_OUTPUT(W5100_CONTROL_PORT_NUM, W5100_CHIPSEL_PIN_NUM, 1);

  //Set read enable pin as an output pin
  IO_CONFIG_OUTPUT(W5100_CONTROL_PORT_NUM, W5100_READ_PIN_NUM, 1);
  
  //Set write enable pin as an output pin
  IO_CONFIG_OUTPUT(W5100_CONTROL_PORT_NUM, W5100_WRITE_PIN_NUM, 1);

  //Set addressing pins as outputs
  IO_CONFIG_OUTPUT(W5100_CONTROL_PORT_NUM, W5100_ADDR0_PIN_NUM, 0);
  IO_CONFIG_OUTPUT(W5100_CONTROL_PORT_NUM, W5100_ADDR1_PIN_NUM, 0);
  
  //Set interrupt pin as input
  IO_CONFIG_INPUT(W5100_CONTROL_PORT_NUM, W5100_INTERRUPT_PIN_NUM, 
                                                    PIN_DEFAULT_TRISTATE);
//!DEBUG  
//  //Enable interrupts for Port 0
//  IEN1  |=  0x20; // IEN1.P0IE = 1
//  
//  //Enable interrupt on P0_6  
//  P0IEN |=  W5100_INTERRUPT_BIT;
  
  //Set the dataport to output mode
  W5100_DATA_MODE = W5100_DATA_MODE_WRITE;
}
/******************************************************************************/
/**
 * This routine uses socket 0 to make the DHCP connection.
 */
static uint8 __hostchannel_initializeEndpoint(void)
{
  //Assume that the operation is going to fail.
  uint8 returnStatus = FAILED;
  
  //Reset the current IP address to 0x00000000
  w5100_setLocalIPAddress(NULL_IP_ADDRESS);

  //Try the open operation 5 times.
  uint8 tries = 5;
  do
  {
    //Set the local port (68 for DHCP)
    w5100_setLocalPortNumber(HOSTCHANNEL_SOCK, 68);
    
    //Reset the destination IP address (broadcast)
    w5100_setRemoteIPAddress(HOSTCHANNEL_SOCK, BROADCAST_IP_ADDRESS);
    
    //Set the destination port number (67 for DHCP)
    w5100_setRemotePortNumber(HOSTCHANNEL_SOCK, 67);
    
    //Open the socket in UDP mode
    w5100_openSocket(HOSTCHANNEL_SOCK, W5100_SOCK_MODE_UDP);
    
    //Give it some time before doing anything.
    utils_sleepUs(10);
    
    //Check if the connection was opened
    if(w5100_getSocketState(HOSTCHANNEL_SOCK) == W5100_SOCK_STATE_UDP)
    {
      //Connection was successful. Try obtaining a DHCP address.
      returnStatus = __hostchannel_obtainIPAddress();
    }
    
    //The connection could not be opened. Close the socket and try again.
    //Close the channel.
    w5100_closeSocket(HOSTCHANNEL_SOCK);
    
    //Wait for sometime before trying again.
    utils_sleepUs(1000);
  }
  while(returnStatus  != SUCCESS && --tries > 0); // Loop until successful, or
                                                  // until try count runs out.
  
  if(returnStatus == SUCCESS)
  {
    //Change channel state to READY
    hostchannel_State = CHANNEL_STATE_READY;
  }
  
  return(returnStatus);
}
/******************************************************************************/
/**
 * Negotiate an IP address with the DHCP server.
 */
static uint8 __hostchannel_obtainIPAddress(void)
{
  //Start the DHCP process - loop until this process completes
  uint8 dhcpAttempts = 10;    
  do
  {
    ip_address_t ipAddress;  
    uint32 sessionId = (utils_getRandom() * 0x10000) + utils_getRandom();
    uint32 serverId = 0x00;
    uint8 type = 0x00;
    dhcp_option_value_t optionValue;

    //Prepare a DHCP discover message and send it.
    dhcp_message_t msg;
    uint16 size = dhcp_buildDiscoverMessage(&msg, HOSTCHANNEL_MAC_ADDRESS, sessionId);
    if(hostchannel_sendData((uint8 *)&msg, size) != SUCCESS)
    {
      //Message was not sent successfully
      continue;
    };
    
    //Wait for the DHCP response
    uint8 tries = 80;
    do
    {
      //See if there is a DHCP response.
      size = __hostchannel_getDHCPResponse(&msg);
      if(size != 0)
      {
        uint8 status = dhcp_validateServerMessage(&msg, size, 
                                                  sessionId, &serverId);
        if(status == SUCCESS)
        {
          //Capture the IP Address.
          ipAddress = network_intToIPAddress(msg.yiaddr);
          optionValue = dhcp_getOption(&msg, size, DHCP_OPT_TYPE);
          if(optionValue.pOption != NULL && optionValue.size == 1)
          {
            type = *(optionValue.pOption);
            if(type == DHCPOFFER)
            {
              break;
            }
          }
        }
      }
      utils_sleepUs(200);
    }
    while(--tries > 0);
    if(type != DHCPOFFER)
    {
      continue;
    }
    
    //A DHCP offer was received. Now send out a DHCP request.
    size = dhcp_buildRequestMessage(&msg, HOSTCHANNEL_MAC_ADDRESS, sessionId, ipAddress, serverId);
    hostchannel_sendData((uint8 *)&msg, size);
    
    //Wait for the DHCP response
    tries = 80;
    do
    {
      //See if there is a DHCP response.
      size = __hostchannel_getDHCPResponse(&msg);
      if(size != 0)
      {
        uint8 status = dhcp_validateServerMessage(&msg, size, 
                                                  sessionId, &serverId);
        if(status == SUCCESS)
        { 
          //Check the DHCP response type
          optionValue = dhcp_getOption(&msg, size, DHCP_OPT_TYPE);
          if(optionValue.pOption != NULL && optionValue.size == 1)
          {
            type = *(optionValue.pOption);
            if(type == DHCPACK ||type == DHCPNAK)
            {
              break;
            }
          }
        }
      }
      utils_sleepUs(200);
    }
    while(--tries > 0);
    
    
    //We have successfully completed an DHCP IP request. Set network
    //parameters on the ethernet device.
    if(type == DHCPACK)
    {     
      //Set local IP address.
      w5100_setLocalIPAddress(ipAddress);
      
      //Check to see if the subnet mask has been provided.
      optionValue = dhcp_getOption(&msg, size, DHCP_OPT_SUBNET);
      if(optionValue.pOption != NULL && optionValue.size == 4)
      {
        //Set the subnet mask.
        memcpy(&ipAddress, optionValue.pOption, optionValue.size);
        w5100_setSubnetMask(ipAddress);
      }
      
      //Check to see if the default gateway ip has been provided.
      optionValue = dhcp_getOption(&msg, size, DHCP_OPT_ROUTERS);
      if(optionValue.pOption != NULL && optionValue.size == 4)
      {
        //Set the subnet mask.
        memcpy(&ipAddress, optionValue.pOption, optionValue.size);
        w5100_setGatewayAddress(ipAddress);
              
        //Store the gateway IP address as the host IP address for the channel.
        hostchannel_hostIPAddress = ipAddress;
      }
      return(SUCCESS);
    }
  }
  while (--dhcpAttempts > 0);
  return(FAILED);
}
/******************************************************************************/
static uint16 __hostchannel_getDHCPResponse(dhcp_message_t *pMessage)
{
  uint16 size = __hostchannel_getRawByteCount();
  if(size > (MAX_DHCP_MSG_SIZE + 8))
  {
    //Data in the buffer is larger than we expected. Read the data
    //and discard it.
    hostchannel_receiveData(NULL, size);
    size=0;
  }
  else if(size > 0)
  {
    uint8 header[8];
    //Read in the UDP header.
    hostchannel_receiveData(header, 8);
    
    //Now read in the DHCP packet
    size-=8;
    hostchannel_receiveData((uint8*)pMessage, size);
  }
  return(size);
}
/******************************************************************************/
static uint16 __hostchannel_getRawByteCount()
{
  uint16 size = 0;
  if(!W5100_INTERRUPT_PIN)
  {
    uint8 flags = w5100_getEthernetEventState();
    if(flags&0x01)
    {
      flags = w5100_getSocketEventState(HOSTCHANNEL_SOCK);
      if(flags&W5100_SOCK_INT_FLAG_RECV)
      {
        size = w5100_getAvailableDataSize(HOSTCHANNEL_SOCK);
      }
    }
  }
  return(size);
}